#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#include <tokenhelper.h>

#include <string>

// stream
static FILE                    *_fp;

static std::string              _fn;

static int                      _lineno;

static std::string              _tk_buf;

static int
_fgetc()
{
    static int r = 0;

    if (_fp) {
        int c = fgetc(_fp);

        switch (c) {
        case '\r':
            ++_lineno;
            r = 1;
            break;

        case '\n':
            if (!r)
                ++_lineno;
            else
                r = 0;
            break;

        default:
            r = 0;
            break;
        }

        return c;
    }

    return EOF;
}

#define HANDLE_LINE_END()                       \
    do {                                        \
        switch (c) {                            \
        case '\r':                              \
            c = _fgetc();                       \
            if (c != EOF) {                     \
                if (c != '\n')                  \
                    ungetc(c, _fp);             \
            }                                   \
            return 0;                           \
                                                \
        case '\n':                              \
            return 0;                           \
        }                                       \
    } while (0)

static int
_skip_block_string()
{
    int c;
    while ((c = _fgetc()) != EOF) {
        if (c == ']') {
            c = _fgetc();
            if (c == ']')
                break;
        }
    }

    return 0;
}

// skip comment
static int
_skip_comment()
{
    int c;
    int multi_line_comment_p = 0;

    // check if it's multi-line comment
    c = _fgetc();
    if (c == EOF)
        return 0;

    if (c == '[') {
        c = _fgetc();
        if (c == EOF)
            return 0;
        if (c == '[')
            multi_line_comment_p = 1;
        else
            HANDLE_LINE_END();
    }
    else
        HANDLE_LINE_END();

    if (multi_line_comment_p) {
        return _skip_block_string();
    }
    else {
        while ((c = _fgetc()) != EOF) {
            HANDLE_LINE_END();
        }
    }

    return 0;
}

// string
static int
_skip_string(int left)
{
    if (_fp) {
        int c;
        int bs = 0;
        while ((c = _fgetc()) != EOF) {
            if (c == left) {
                if (!bs)
                    break;
            }

            if (bs)
                bs = 0;
            else if (c == '\\')
                bs = 1;
        }
    }
    return 0;
}

enum {
    TK_NONE = 0,

    TK_IGNORE,

    TK_IDENT,

    TK_LEFTP,

    TK_RIGHTP,
};

static int
_next_token(int *lineno)
{
    if (!_fp)
        return TK_NONE;

    _tk_buf.clear();

    int c;
    for (;;) {
        c = _fgetc();

        switch (c) {
        default:
            if ((c >= 'a' && c <= 'z')
                || (c >= 'A' && c <= 'Z')
                || (c == '_')) {
                _tk_buf.push_back(c);

                if (lineno)
                    *lineno = _lineno;

                while ((c = _fgetc()) != EOF) {
                    if ((c >= 'a' && c <= 'z')
                        || (c >= 'A' && c <= 'Z')
                        || (c >= '0' && c <= '9')
                        || (c == '_' || c == '.')) {
                        _tk_buf.push_back(c);
                    }
                    else {
                        if (c != EOF && c != '\r' && c != '\n')
                            ungetc(c, _fp);
                        break;
                    }
                }

                return TK_IDENT;
            }
            else
                return TK_IGNORE;
            break;

        case ' ':
        case '\t':
        case '\r':
        case '\n':
        case '\f':
            break;

        case '"':
            _skip_string('"');
            return TK_IGNORE;
            break;

        case '\'':
            _skip_string('\'');
            return TK_IGNORE;
            break;

        case '-':
            c = _fgetc();
            if (c == '-')
                _skip_comment();
            else {
                if (c != EOF && c != '\r' && c != '\n')
                    ungetc(c, _fp);
                    
            }
            break;

        case '[':
            c = _fgetc();
            if (c == '[')
                _skip_block_string();
            else {
                if (c != EOF && c != '\r' && c != '\n')
                    ungetc(c, _fp);
            }
            return TK_IGNORE;
            break;

        case '(':
            return TK_LEFTP;
            break;

        case ')':
            return TK_RIGHTP;
            break;

        case EOF:
            return TK_NONE;
            break;
        }
    }

    // never go here
    return TK_NONE;
}

// interface
int tk_open_file(const char *fn)
{
    if (_fp)
        fclose(_fp);

    _fp = fopen(fn, "r");

    _lineno = 1;

    _fn = fn;

    return _fp ? 0 : -1;
}

int tk_close_file()
{
    if (_fp) {
        fclose(_fp);
        _fp = NULL;
    }

    return 0;
}

int tk_fetch_next(int *lineno, std::string& token)
{
    std::string last_tk;
    int         last_type;
    int         last_line;
    int         last_is_func;
    int         t;

    last_is_func = 0;
    while ((t = _next_token(lineno)) != TK_NONE) {
        switch (t) {
        case TK_LEFTP:
            if (last_type == TK_IDENT) {
                *lineno = last_line;
                token = last_tk;
                return TT_REF;
            }
            break;

        case TK_IDENT:
            if (last_is_func) {
                token = _tk_buf;
                return TT_FUNC;
            }
            break;
        }

        last_tk = _tk_buf;
        last_line = *lineno;
        last_type = t;

        last_is_func = (last_tk == "function");
    }

    return TT_NONE;
}
